docs(design): wire observability and debug host - #315
Conversation
727e0cc to
cccd69d
Compare
TarikGul
left a comment
There was a problem hiding this comment.
I think generally the content of the doc is good - and the direction is nice. But I think we should reframe it more like a specification. Currently its very easy for the doc to get out of sync with the implementation PR.
I think we can really slim this down and take out some of the unnecessary sections that dont add much to the actual core design of what we are building.
|
Two points on the design. Both are about where the debugger lives. 1. Topology: the host dials the debuggerThe doc's model is a passive This is not a preference, it's forced. Only the inside can initiate:
So the debugger app is always the server, and every host dials outward: Two properties to state as invariants, neither currently in the doc:
Worth noting that the second invariant is also the extension point. If we later want mocking or What this removes from the current design: the relay service ( Two things this forces that the doc should state. The debugger URL must be configurable, not And that LAN hop is why 2. The tap belongs in
|
| Direction | Choke point |
|---|---|
| inbound (product → core) | ProductRuntime::receive_frame() — host_core.rs:422 |
| outbound (core → product) | FrameSink::emit_frame() — host_core.rs:40 |
Native reaches these through ws_bridge.rs (WsFrameSink), web through wasm.rs:851. So one tap
implementation covers both platforms, and nothing in @parity/truapi changes at all. That is the test
for whether the product is genuinely untouched, and the current design fails it.
A sink trait, not a hardcoded socket
Worth defining the flush target as a trait rather than wiring a WebSocket into the core, both to keep
the transport out of truapi-server and because wire frames are not the only thing worth observing.
dotli's own debugger also surfaces host-internal events like SSO, and those have no wire frame to hang
off. An enum event keeps room for them without reopening the design:
/// Dev-only sink for host debug events. A host that does not enable the
/// debugger leaves it unset and the tap is inert.
pub trait DebugSink: Send + Sync {
/// Hand one event to the sink. Fire-and-forget by construction: it cannot
/// block the frame path and cannot fail the operation that produced it.
fn emit(&self, event: DebugEvent);
}
/// One observable host event.
pub enum DebugEvent {
/// A SCALE wire frame crossing a product channel.
Frame {
/// Which product channel on this host.
channel_id: ChannelId,
/// Product → core, or core → product.
dir: FrameDirection,
/// Untouched `ProtocolMessage` bytes; the debugger app decodes.
bytes: Vec<u8>,
},
}13048f9 to
4f5c8f7
Compare
|
Moved the wire debug tap into the Rust host (truapi-server's DebugSink) - tapping both frame choke points |
ffa3d98 to
b690573
Compare
9a74f5b to
b509e63
Compare
b509e63 to
723fdfd
Compare
Specifies the wire observability layer for @parity/truapi: where the tap may live, what a sink may and may not do, the envelope and wire-contract identity carried with it, how frames are correlated, how each surface is confined, and how enablement is read and reported. The doc fixes the contract rather than walking through the implementation.
Rewritten as a specification per @TarikGul's review: 54 normative statements across 12 sections. The version reviewed on 28 Jul had none, which is what allowed it to drift from the code.
On "slim this down": the sections called out as fluff are gone — the testing section, the
examples/relay-server.mjsreference (deleted in #295), the?debugquery-param gate, the "dead-code-eliminable" claim — and rationale in §3, §6, §7 and §8 is compressed by roughly 12 lines. §9 is new and three requirements were added, so the net is 285 → 312 lines. §6's confinement rationale and §8(C) are the remaining candidates if a further cut is wanted.Changes in this push:
§7 asserted a mechanism the code did not have. It said the bundler replaces the DEV condition with a literal. The shipped code read the flag through an alias, which no bundler substitutes, so the tap was disabled in every build and the production guarantee held by accident. Fixed in feat(truapi-server): host-side wire debug tap and debugger #295; the doc now requires the bare
import.meta.env.DEVtoken, since violating it fails silently in both directions.@Imod7's gating comment is resolved at the root. There is no query-param gate and no
window.__truapiWireDebugger__global in the design. Enablement is build-time only; the runtime switch controls where to dial, never whether to decode.@TarikGul on dead-code elimination: the claim is replaced by a testable property — absent from the production bundle. A production build of dot.li contains zero occurrences of the inspector's own strings.
@pgherveou on native apps: §9 no longer assumes a browser. Enablement is host-specific (per-origin store for a browser host, injected value for a native one) and the requirement covers both.
§7's "the package is private" is replaced by the property that does the work: absent from the production bundle. Publishing does not weaken the guarantee.
§8(A) specified a CLI/REPL that was dropped; the HTTP endpoints are the shared contract. §8(B) said the embed carries less chrome than the standalone; it carries the same, over one renderer and one engine.
§3 requires every producer to stamp identity, including a same-page tee. A tap on the far side of a worker boundary cannot read the core's hash by construction, so the core must expose it outward. Without this the two surfaces are asymmetric and the embedded one can never decode.
§9 is new: enablement, and saying so. A host must report whether it dialled and from what source. Silence is indistinguishable from success: the debugger's own viewer holds a socket, so an empty board with a live socket reads as a broken debugger rather than a host that was never enabled.
Doc: docs/design/wire-observability-debug-host.md
Implementation: feat(truapi-server): host-side wire debug tap and debugger #295